package com.tang.subject.infra.basic.service.impl;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.tang.common.entity.PageResult;
import com.tang.component.es.EsIndexInfo;
import com.tang.component.es.EsRestClient;
import com.tang.component.es.EsSearchRequest;
import com.tang.component.es.EsSourceData;
import com.tang.subject.common.enums.SubjectTypeEnum;
import com.tang.subject.infra.basic.entity.es.EsSubjectInfoFields;
import com.tang.subject.infra.basic.entity.es.SubjectInfoEs;
import com.tang.subject.infra.basic.service.SubjectEsService;
import org.apache.commons.collections4.MapUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class SubjectEsServiceImpl implements SubjectEsService {


    @Value("${es.load.clusterName}")
    private String esClusterName;

    @Value("${es.load.index}")
    private String esIndexName;

    /**
     * 向es插入文档
     *
     * @param subjectInfoEs 待插入的数据
     * @return boolean
     * @author Tang
     * @date 2024/2/23 20:38
     **/
    @Override
    public boolean insert(SubjectInfoEs subjectInfoEs) {
        EsIndexInfo esIndexInfo = getEsIndexInfo();
        EsSourceData sourceData = getEsSourceData(subjectInfoEs);
        return EsRestClient.insertDoc(esIndexInfo, sourceData);
    }

    /**
     * 在es中对关键字全文检索
     *
     * @param subjectInfoEs 查询条件
     * @return com.tang.common.entity.PageResult<com.tang.subject.infra.basic.entity.es.SubjectInfoEs>
     * @author Tang
     * @date 2024/2/24 13:36
     **/
    @Override
    public PageResult<SubjectInfoEs> querySubjectList(SubjectInfoEs subjectInfoEs) {
        //创建分页结果对象
        PageResult<SubjectInfoEs> pageResult = new PageResult<>();
        pageResult.setPageNo(subjectInfoEs.getPageNo());
        pageResult.setPageSize(subjectInfoEs.getPageSize());
        //创造es检索对象
        EsSearchRequest esSearchRequest = createSearch(subjectInfoEs);
        SearchResponse searchResponse = EsRestClient.searchWithTermQuery(getEsIndexInfo(), esSearchRequest);
        //为空则检索失败
        if (Objects.isNull(searchResponse)
                || Objects.isNull(searchResponse.getHits())
                || Objects.isNull(searchResponse.getHits().getHits())) {
            return pageResult;
        }
        SearchHits searchResponseHits = searchResponse.getHits();
        SearchHit[] hits = searchResponseHits.getHits();
        //包装对象
        List<SubjectInfoEs> res = Arrays.stream(hits)
                .map(this::convertHitToInfo)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        int total = Long.valueOf(searchResponseHits.getTotalHits().value).intValue();
        pageResult.setRecords(res);
        pageResult.setTotal(total);
        return pageResult;
    }

    private SubjectInfoEs convertHitToInfo(SearchHit hit) {
        try {
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();
            if (MapUtils.isEmpty(sourceAsMap)) {
                return null;
            }
            SubjectInfoEs subjectInfoEs = new SubjectInfoEs();
            subjectInfoEs.setSubjectId(MapUtils.getLong(sourceAsMap, EsSubjectInfoFields.SUBJECT_ID));
            subjectInfoEs.setSubjectName(MapUtils.getString(sourceAsMap, EsSubjectInfoFields.SUBJECT_NAME));
            subjectInfoEs.setSubjectAnswer(MapUtils.getString(sourceAsMap, EsSubjectInfoFields.SUBJECT_ANSWER));
            subjectInfoEs.setSubjectType(MapUtils.getString(sourceAsMap, EsSubjectInfoFields.SUBJECT_TYPE));
            subjectInfoEs.setScore(new BigDecimal(String.valueOf(hit.getScore())).multiply(new BigDecimal("100.00")
                    .setScale(2, RoundingMode.HALF_UP)));
            subjectInfoEs.setDocId(MapUtils.getLong(sourceAsMap, EsSubjectInfoFields.DOC_ID));
            //高亮处理
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            highlightHandle(highlightFields, subjectInfoEs, EsSubjectInfoFields.SUBJECT_NAME);
            highlightHandle(highlightFields, subjectInfoEs, EsSubjectInfoFields.SUBJECT_ANSWER);
            return subjectInfoEs;
        } catch (Exception e) {
            return null;
        }
    }

    private void highlightHandle(Map<String, HighlightField> highlightFields, SubjectInfoEs subjectInfoEs, String fieldName) throws Exception {
        HighlightField highlightField = highlightFields.get(fieldName);
        if (Objects.isNull(highlightField)) {
            return;
        }
        Text[] fragments = highlightField.getFragments();
        StringBuilder sb = new StringBuilder();
        for (Text fragment : fragments) {
            sb.append(fragment);
        }
        fieldName = StringUtils.underlineToCamel(EsSubjectInfoFields.SUBJECT_NAME);
        Field field = subjectInfoEs.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(subjectInfoEs, sb.toString());
        field.setAccessible(false);
    }

    private EsSearchRequest createSearch(SubjectInfoEs subjectInfoEs) {
        EsSearchRequest esSearchRequest = new EsSearchRequest();
        //创建查询条件匹配汇总器
        BoolQueryBuilder bq = getBoolQueryBuilder(subjectInfoEs);
        //高亮处理
        HighlightBuilder highlightBuilder = new HighlightBuilder()
                .field("*")
                .requireFieldMatch(false);
        highlightBuilder.preTags("<span style = \"color:yellow\">");
        highlightBuilder.postTags("</span>");
        //组装es请求对象
        esSearchRequest.setBq(bq);
        esSearchRequest.setHighlighter(highlightBuilder);
        esSearchRequest.setFields(EsSubjectInfoFields.FIELD_QUERY);
        esSearchRequest.setFrom((subjectInfoEs.getPageNo() - 1) * subjectInfoEs.getPageSize());
        esSearchRequest.setSize(subjectInfoEs.getPageSize());
        return esSearchRequest;
    }

    private BoolQueryBuilder getBoolQueryBuilder(SubjectInfoEs subjectInfoEs) {
        BoolQueryBuilder bq = new BoolQueryBuilder();
        //创建查询条件匹配器
        //题目名称
        MatchQueryBuilder subjectNameMatch = QueryBuilders
                .matchQuery(EsSubjectInfoFields.SUBJECT_NAME, subjectInfoEs.getKeyWord());
        bq.should(subjectNameMatch);
        //题目答案
        MatchQueryBuilder subjectAnswerMatch = QueryBuilders
                .matchQuery(EsSubjectInfoFields.SUBJECT_ANSWER, subjectInfoEs.getKeyWord());
        bq.should(subjectAnswerMatch);
        //题目类型
        MatchQueryBuilder subjectTypeMatch = QueryBuilders
                .matchQuery(EsSubjectInfoFields.SUBJECT_TYPE, SubjectTypeEnum.BRIEF.code);
        bq.must(subjectTypeMatch);
        bq.minimumShouldMatch(1);
        return bq;
    }

    private EsSourceData getEsSourceData(SubjectInfoEs subjectInfoEs) {
        EsSourceData esSourceData = new EsSourceData();
        Map<String, Object> data = new HashMap<>();
        data.put(EsSubjectInfoFields.DOC_ID, subjectInfoEs.getDocId());
        data.put(EsSubjectInfoFields.SUBJECT_ID, subjectInfoEs.getSubjectId());
        data.put(EsSubjectInfoFields.SUBJECT_NAME, subjectInfoEs.getSubjectName());
        data.put(EsSubjectInfoFields.SUBJECT_ANSWER, subjectInfoEs.getSubjectAnswer());
        data.put(EsSubjectInfoFields.SUBJECT_TYPE, subjectInfoEs.getSubjectType());
        data.put(EsSubjectInfoFields.CREATE_TIME, subjectInfoEs.getCreateTime());
        data.put(EsSubjectInfoFields.CREATE_USER, subjectInfoEs.getCreateUser());
        esSourceData.setData(data);
        esSourceData.setDocId(subjectInfoEs.getDocId().toString());
        return esSourceData;
    }

    private EsIndexInfo getEsIndexInfo() {
        EsIndexInfo esIndexInfo = new EsIndexInfo();
        esIndexInfo.setIndexName(esIndexName);
        esIndexInfo.setNodeName(esClusterName);
        return esIndexInfo;
    }
}
